//------------------------------------------------------------------------------
// <copyright file="RpcServerImpl.h" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------

#pragma once

#include "RpcServer.h"
#include "RpcArchive.h"
#include "RpcArchiveImpl.h"
#include "RpcArchiveImplFactory.h"
#include "IRpcMessageHandler.h"
#include <map>
#include <unordered_map>
#include <concurrent_queue.h>
#include <mutex>

namespace Microsoft { namespace Internal { namespace GamesTest { namespace Rpc { namespace Server { namespace Ut
{
	class RpcServerImplTests;
	class RpcBuiltInHooksTests;

}}}}}}

namespace Microsoft { namespace Internal { namespace GamesTest { namespace Rpc { namespace Server
{
	class RpcServerImpl : public IRpcMessageHandler
	{
	private:
		friend RpcHookCode SetMaximumQueueSize(RpcArchiveImpl& input, RpcArchiveImpl& output, RpcServerImpl* server);
		friend RpcHookCode UpdateSingleEvent(std::weak_ptr<IRpcChannel> client, RpcServerImpl* server, const std::wstring& eventName, bool subscribe);
		friend RpcHookCode EnumRegisteredHooks(RpcArchiveImpl& input, RpcArchiveImpl& output, std::weak_ptr<IRpcChannel> client, RpcServerImpl* server);

		friend class Microsoft::Internal::GamesTest::Rpc::Server::Ut::RpcServerImplTests;
		friend class Microsoft::Internal::GamesTest::Rpc::Server::Ut::RpcBuiltInHooksTests;

		typedef std::unordered_multimap<std::wstring, std::weak_ptr<IRpcChannel>> RpcSubscriberTable;

		RpcSubscriberTable m_subscriberTable;

		typedef std::function<RpcHookCode (RpcArchiveImpl&, RpcArchiveImpl&, std::weak_ptr<IRpcChannel>)> RpcHookFunction;
		typedef std::pair<RpcHookFunction, bool> RpcHookEntry;
		typedef std::map<std::wstring, RpcHookEntry> RpcHookTable;

		RpcHookTable m_hookTable;
		int m_numConnectedClients;
		size_t m_maxQueueSize;
		bool m_isStarted;
		int m_port;
		std::mutex m_reEntryLock;
		bool m_immediateHooksRegistered;
		static const GUID TdkRpcDefaultExtensionId;

		GUID m_extensionId;

		enum class EnabledStateType { Uninitialized, Disabled, Enabled };

		static EnabledStateType s_enabledState;

	public:
		/// <summary> 
		/// Initializes a new instance of the RpcServerImpl class.
		/// </summary>
		RpcServerImpl();

		/// <summary> 
		/// Initializes a new instance of the RpcServerImpl class with a specific port number.
		/// <param name="port">A port number to listen on.</param>
		/// </summary>
		RpcServerImpl(int port);

		/// <summary> 
		/// Initializes a new instance of the RpcServerImpl class.
		/// <param name="serverId">A unique identifier for this server instance.
		/// The same identifier should be used on the client to connect to this server.</param>
		/// </summary>
		RpcServerImpl(const GUID& serverId);

		/// <summary> 
		/// Initializes a new instance of the RpcServerImpl class with a specific port number.
		/// <param name="serverId">A unique identifier for this server instance.
		/// The same identifier should be used on the client to connect to this server.</param>
		/// <param name="port">A port number to listen on.</param>
		/// </summary>
		RpcServerImpl(const GUID& serverId, int port);

		/// <summary> 
		/// Initializes a new instance of the RpcServerImpl class.
		/// </summary>
		/// <param name="maxQueueSize">Maximum queue size for the server to start with.</param>
		RpcServerImpl(unsigned int maxQueueSize);

		/// <summary>
		/// Initializes a new instance of the RpcServerImpl class with a specific port number.
		/// <param name="port">A port number to listen on.</param>
		/// <param name="maxQueueSize">Maximum queue size for the server to start with.</param>
		/// </summary>
		RpcServerImpl(int port, unsigned int maxQueueSize);

		/// <summary> 
		/// Initializes a new instance of the RpcServerImpl class.
		/// <param name="serverId">A unique identifier for this server instance.
		/// <param name="maxQueueSize">Maximum queue size for the server to start with.</param>
		/// The same identifier should be used on the client to connect to this server.</param>
		/// </summary>
		RpcServerImpl(const GUID& serverId, unsigned int maxQueueSize);

		/// <summary> 
		/// Initializes a new instance of the RpcServerImpl class with a specific port number.
		/// <param name="serverId">A unique identifier for this server instance.
		/// The same identifier should be used on the client to connect to this server.</param>
		/// <param name="port">A port number to listen on.</param>
		/// <param name="maxQueueSize">Maximum queue size for the server to start with.</param>
		/// </summary>
		RpcServerImpl(const GUID& serverId, int port, unsigned int maxQueueSize);

		/// <summary>
		/// The method to be invoked when any message is received from a client. The message will be directed to the appropriate processing
		/// queue or processed immediately if appropriate.
		/// </summary>
		virtual HRESULT ProcessIncomingMessage(std::weak_ptr<IRpcChannel> channel, const std::wstring& message);

		/// <summary>
		/// Synchronously processes all pending requests and raise queued events.
		/// </summary>
		virtual HRESULT ProcessPendingRequests();

		/// <summary>
		/// This method should do any work necessary to clean up resources being held for a client that has disconnected.
		/// </summary>
		virtual void ClientDisconnected(std::weak_ptr<IRpcChannel> client);

		/// <summary> 
		/// Start this sever and begin listening for incoming messages.
		/// </summary>
		GAMESTEST_ANNOTATE_HRESULT_METHOD
			HRESULT Start();

		/// <summary> 
		/// Register a method with this server so that it can be invoked by remote clients.
		/// </summary>
		/// <param name="hookName">The name of the method on the remote interface.  
		/// Clients will use this name to invoke the hook regardless of the name of the
		/// method passed in the <paramref name="hookFunc" /> parameter.</param>
		/// <param name="hookFunc">A function that calls, on the appropriate channel, a method to be made available to remote clients.</param>
		/// <param name="immediate">Whether to process requests for this hook on the client receiving thread.</param>
		GAMESTEST_ANNOTATE_HRESULT_METHOD
			HRESULT RegisterHookMethod(_In_z_ const wchar_t* hookName, _In_ RpcHookFunction hookFunc, bool immediate);

		/// <summary>
		/// Stops this server so that it no longer listens for messages.
		/// </summary>
		GAMESTEST_ANNOTATE_HRESULT_METHOD
			HRESULT Stop();

		/// <summary>
		/// Raise an event to all subscribing clients.
		/// </summary>
		GAMESTEST_ANNOTATE_HRESULT_METHOD
			HRESULT RaiseEvent(_In_z_ const wchar_t* eventName, _In_ RpcArchiveImplPtr archive);

		/// <summary>
		/// Gets a value indicating whether or not this server has been started.
		/// </summary>
		bool GetIsStarted();

		/// <summary>
		/// Gets a value indicating whether or not this server has been started.
		/// </summary>
		__declspec( property(get=GetIsStarted)) bool IsStarted;

		/// <summary>
		/// Gets the id of this RpcServer.
		/// </summary>
		const GUID& GetServerId();

		/// <summary>
		/// Gets the default id of this RpcServer.
		/// </summary>
		static const GUID& GetDefaultServerId();

		/// <summary>
		/// Gets the port number this RpcServer is listening on.
		/// </summary>
		int GetPort();

		/// <summary>
		/// Gets the default listening port for RpcServer.
		/// </summary>
		static int GetDefaultPort();
		
		/// <summary>
		/// Gets a value indicating whether or not RpcServer methods have been enabled.
		/// </summary>
		static bool GetIsEnabled();

		/// <summary>
		/// Gets the default max. queue size for RpcServer.
		/// </summary>
		static unsigned int GetDefaultMaxQueueSize();

		/// <summary>
		/// Gets the id of this RpcServer.
		/// </summary>
		__declspec(property(get=GetServerId)) const GUID& ServerId;

		/// <summary>
		/// Gets the port of this RpcServer.
		/// </summary>
		__declspec(property(get=GetPort)) int Port;

		/// <summary>
		/// Gets the default port of this RpcServer.
		/// </summary>
		__declspec(property(get=GetDefaultPort)) int DefaultPort;

		/// <summary>
		/// Gets the default max. queue size of this RpcServer.
		/// </summary>
		__declspec(property(get=GetDefaultMaxQueueSize)) unsigned int DefaultMaxQueueSize;

		~RpcServerImpl();

	private:
		RpcServerImpl(const RpcServerImpl& other);
		RpcServerImpl& operator=(const RpcServerImpl& other);

		struct PendingIncomingRequest
		{
			std::weak_ptr<IRpcChannel> channel;
			std::wstring request;
		};

		struct PendingOutgoingRequest
		{
			std::wstring eventName;
			RpcArchiveImplPtr archive;

			PendingOutgoingRequest()
				: eventName(), archive(std::move(RpcArchiveImplFactory::EmptyPointer()))
			{
			}

			PendingOutgoingRequest(const wchar_t* name, RpcArchiveImplPtr archive)
				: eventName(name), archive(std::move(archive))
			{
			}

			PendingOutgoingRequest(const PendingOutgoingRequest& other)
				: eventName(other.eventName), archive(RpcArchiveImplFactory::Allocate())
			{
				*archive = *other.archive;
			}
			
			PendingOutgoingRequest( PendingOutgoingRequest&& other)
				: eventName(std::move(other.eventName)), archive(std::move(other.archive))
			{
			}

			PendingOutgoingRequest& operator=(const PendingOutgoingRequest& other)
			{
				// check for self-assignment
				if(&other == this)
					return *this;

				eventName = other.eventName;
				*archive = *other.archive;

				return *this;
			}

			PendingOutgoingRequest& operator=( PendingOutgoingRequest&& other)
			{
				// check for self-assignment
				if(&other == this)
					return *this;

				eventName = std::move(other.eventName);
				archive = std::move(other.archive);
				return *this;
			}
		};

		typedef concurrency::concurrent_queue<PendingIncomingRequest> PendingIncomingRequestContainer;
		PendingIncomingRequestContainer m_pendingIncomingRequests;

		typedef concurrency::concurrent_queue<PendingOutgoingRequest> PendingOutgoingRequestContainer;
		PendingOutgoingRequestContainer m_pendingOutgoingRequests;

#ifdef _WINRT_DLL
		static RpcServerImpl* GetImplementation(RpcServer^ server);
#else
		/// <summary>
		/// Gets the internal implementation object given RpcServer instance, useful for unit tests.
		/// </summary>
		static RpcServerImpl* GetImplementation(RpcServer* server);
#endif

		/// <summary>
		/// Register built-in hooks used for handshake with the client, subscribing to events, and so on.
		/// </summary>
		HRESULT RegisterBuiltInHookMethods();

		/// <summary>
		/// The method to be invoked when a 'request' message is received from a client.  The request shall be placed into a collection and
		/// optionally processed when the 'ProcessPendingRequests' method is called.
		/// </summary>
		HRESULT QueueIncomingRequest(std::weak_ptr<IRpcChannel> channel, std::wstring&& request);

		HRESULT ProcessPendingRequest(const PendingIncomingRequest& pendingRequest);
		HRESULT ProcessPendingRequest(PendingOutgoingRequest& pendingRequest);

#ifdef _DURANGO
		/// <summary>
		/// This method abstracts the getting of the sandboxid so it can be faked during unit tests.
		/// </summary>
		static std::wstring GetSandboxId();
#endif
	};

}}}}}